package com.zzyl.nursing.service.impl;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.common.utils.DateUtils;
import com.zzyl.nursing.domain.AlertData;
import com.zzyl.nursing.domain.DeviceData;
import com.zzyl.nursing.mapper.DeviceMapper;
import com.zzyl.nursing.service.IAlertDataService;
import com.zzyl.nursing.vo.AlertNotifyVo;
import com.zzyl.nursing.websocket.WebSocketServer;
import com.zzyl.system.mapper.SysUserRoleMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.zzyl.nursing.mapper.AlertRuleMapper;
import com.zzyl.nursing.domain.AlertRule;
import com.zzyl.nursing.service.IAlertRuleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 报警规则Service业务层处理
 *
 * @author ruoyi
 * @date 2025-09-21
 */
@Slf4j
@Service
public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService {
    @Autowired
    private AlertRuleMapper alertRuleMapper;
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;
    @Autowired
    private DeviceMapper deviceMapper;
    @Autowired
    private SysUserRoleMapper sysUserRoleMapper;
    @Autowired
    private IAlertDataService alertDataService;
    @Autowired
    private WebSocketServer webSocketServer;

    @Value("${alert.deviceMaintainerRole}")
    private String deviceMaintainerRoleName;
    @Value("${alert.managerRole}")
    private String managerRoleName;

    /**
     * 查询报警规则
     *
     * @param id 报警规则主键
     * @return 报警规则
     */
    @Override
    public AlertRule selectAlertRuleById(Long id) {
        return getById(id);
    }

    /**
     * 查询报警规则列表
     *
     * @param alertRule 报警规则
     * @return 报警规则
     */
    @Override
    public List<AlertRule> selectAlertRuleList(AlertRule alertRule) {
        return alertRuleMapper.selectAlertRuleList(alertRule);
    }

    /**
     * 新增报警规则
     *
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int insertAlertRule(AlertRule alertRule) {
        return save(alertRule) ? 1 : 0;
    }

    /**
     * 修改报警规则
     *
     * @param alertRule 报警规则
     * @return 结果
     */
    @Override
    public int updateAlertRule(AlertRule alertRule) {
        return updateById(alertRule) ? 1 : 0;
    }

    /**
     * 批量删除报警规则
     *
     * @param ids 需要删除的报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleByIds(Long[] ids) {
        return removeByIds(Arrays.asList(ids)) ? 1 : 0;
    }

    /**
     * 删除报警规则信息
     *
     * @param id 报警规则主键
     * @return 结果
     */
    @Override
    public int deleteAlertRuleById(Long id) {
        return removeById(id) ? 1 : 0;
    }

    /**
     * 设备数据报警过滤(定时任务 一分钟运行一次)
     */
    @Override
    public void deviceDataAlertFilter() {
        // 1. 查询所有启用的规则数量 若有启用的规则则进行过滤 没有直接返回
        Long count = this.lambdaQuery().eq(AlertRule::getStatus, 1).count();
        if (count <= 0) {
            return;
        }
        // 2. 从redis中获取所有设备最新一次的上报数据
        List<Object> deviceLastDataList = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_LAST_DATA);
        // 3. 数据为空直接返回
        if (ObjectUtil.isEmpty(deviceLastDataList)) {
            return;
        }
        // 4. 不为空 将数据的所有设备的对应功能数据 放到待检查集合中（数据格式标准化处理）
        List<DeviceData> unCheckedDeviceDataList = new ArrayList<>();
        deviceLastDataList.forEach(deviceData -> {
            unCheckedDeviceDataList.addAll((List<DeviceData>) deviceData);
        });
        // 判断 大key 对应的小key 对应的数据不为空
        if (CollectionUtil.isEmpty(unCheckedDeviceDataList)) {
            return;
        }
        // 4. 遍历所有设备功能数据（待检查集合） 检查是否进行需要数据报警处理（检测是否触发报警规则）
        unCheckedDeviceDataList.forEach(item -> {
            alertCheck(item);
        });
    }


    /**
     * 每条数据的报警规则检查
     *
     * @param deviceData
     */
    private void alertCheck(DeviceData deviceData) {
        // 1. 获取设备功能数据的上报时间 如果超过1min 说明设备关闭 跳过检查下一条数据
        LocalDateTime alarmTime = deviceData.getAlarmTime();
        long betweenTime = LocalDateTimeUtil.between(alarmTime, LocalDateTime.now(), ChronoUnit.MINUTES);
        if (betweenTime > 1) {
            return;
        }

        // 2. 获取设备功能对应的报警规则
        // 2.1 获取设备所属的全部设备的报警规则
        List<AlertRule> commonAlertRuleList = lambdaQuery().eq(AlertRule::getProductKey, deviceData.getProductKey())
                .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                .eq(AlertRule::getStatus, 1)
                .eq(AlertRule::getIotId, -1)
                .list();

        // 2.2 获取设备专属的报警规则
        List<AlertRule> uniqueAlertRuleList = lambdaQuery().eq(AlertRule::getProductKey, deviceData.getProductKey())
                .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                .eq(AlertRule::getStatus, 1)
                .eq(AlertRule::getIotId, deviceData.getIotId())
                .list();
        // 2.3 合并设备功能对应的报警规则
        Collection<AlertRule> allAlertRules = CollUtil.addAll(commonAlertRuleList, uniqueAlertRuleList);
        // 3. 如果规则为空 跳过检查下条数据
        if (CollectionUtil.isEmpty(allAlertRules)) {
            return;
        }
        // 4. 遍历报警规则 开始判断该规则是否满足该报警条件
        allAlertRules.forEach(alertRule -> {
            deviceDataAlertRuleCheck(deviceData, alertRule);
        });
    }

    /**
     * 使用报警规则对上报数据进行检测
     *
     * @param deviceData
     * @param alertRule
     */
    private void deviceDataAlertRuleCheck(DeviceData deviceData, AlertRule alertRule) {
        // 1. 判断当前规则是否在生效范围内，不在直接返回 判断下一条报警规则
        String[] split = alertRule.getAlertEffectivePeriod().split("~");
        String startTime = split[0];
        String endTime = split[1];
        LocalTime alarmTime = deviceData.getAlarmTime().toLocalTime();
        if (alarmTime.isBefore(LocalTime.parse(startTime)) || alarmTime.isAfter(LocalTime.parse(endTime))) {
            return;
        }
        // 2. 判断当前规则是否触发报警阈值 没有触发 清空redis中的报警数 直接返回 判断下一条报警规则
        // 2.1 组装 redis报警数 key = 前缀 + 设备ID + 功能ID + 报警规则ID
        String alertCount = CacheConstants.IOT_COUNT_ALERT + deviceData.getIotId() + ":" + deviceData.getFunctionId() + ":" + alertRule.getId();
        // 2.2 是否到达报警阈值 【dataValue 操作符 阈值】
        Double value = Double.valueOf(deviceData.getDataValue());
        // 2.3 获取数据值和阈值的比较结果 -1 0 1
//        int i = value.compareTo(alertRule.getValue());
        int compareResult = NumberUtil.compare(value, alertRule.getValue());
        // 2.4 判断结果
        if ((">=".equals(alertRule.getOperator()) && compareResult >= 0) || ("<".equals(alertRule.getOperator()) && compareResult <= 0)) {
            // 触发阈值，继续流程
            log.info("设备数据：{}触发了报警规则：{}", deviceData, alertRule);
        } else {
            // 未触发阈值，清空redis中的报警数 直接返回 判断下一条报警规则
            redisTemplate.delete(alertCount);
            return;
        }
        // 3. 判断当前规则是否在沉默周期范围内，在直接返回 跳过该条数据
        // 3.1 组装 redis中的沉默周期 key = 前缀 + 设备ID + 功能ID + 沉默周期ID
        String alertSilent = CacheConstants.IOT_SILENT_ALERT + deviceData.getIotId() + ":" + deviceData.getFunctionId() + ":" + alertRule.getId();
        // 3.2 判断redis中是否有沉默周期
        if (redisTemplate.hasKey(alertSilent)) {
            return;
        }
        // 4. redis中没有沉默周期 说明不在沉默周期内 redis中的报警数加1
        Long count = redisTemplate.opsForValue().increment(alertCount);
        // 5. 判断redis中的报警数是否到达持续周期 没有到达 直接返回
        if (count < alertRule.getDuration()) {
            return;
        }
        // 6. 到达持续周期 说明即将开始报警 删除redis中的报警数 并添加redis中的沉默周期
        redisTemplate.delete(alertCount);
//        redisTemplate.opsForValue().set(alertSilent, "1", alertRule.getAlertSilentPeriod(), TimeUnit.MINUTES);
        redisTemplate.opsForValue().set(alertSilent, "1", Duration.ofMinutes(alertRule.getAlertSilentPeriod()));
        // 7.找到需要通知的告警接收人 获取设备功能数据对应的报警接收人id集合
            // 7.1 查看是否是老人异常数据
        Integer alertDataType = alertRule.getAlertDataType();
        List<Long> nursingIds = new ArrayList<>();
        if (alertDataType == 0) {
            if (deviceData.getLocationType() == 0 && deviceData.getPhysicalLocationType() == 3) {
                // 7.1.1 是老人异常数据 判断是固定设备（睡眠监测带）还是随身设备（手表） 随身设备
                nursingIds = deviceMapper.selectNursingIdByIotIdWithElder(deviceData.getIotId());
            } else if (deviceData.getLocationType() == 1 && deviceData.getPhysicalLocationType() == 2) {
                // 7.1.2 是老人异常数据 判断是固定设备（睡眠监测带）还是随身设备（手表） 固定设备
                nursingIds = deviceMapper.selectNursingIdByIotIdWithBed(deviceData.getIotId());
            }
        }else {
            // 7.2 是设备异常数据
            nursingIds = sysUserRoleMapper.selectByRoleName(deviceMaintainerRoleName);
        }
            // 7.3 获取所有角色为超级管理员的id集合
        List<Long> managerRoleIds = sysUserRoleMapper.selectByRoleName(managerRoleName);
            // 7.4 合并所有要通知的用户id集合 需要去重
        List<Long> needNotifyIds = CollUtil.distinct(CollUtil.addAll(nursingIds, managerRoleIds));
        // 8. 向alert data 表中批量插入报警数据
        AlertData alertData = batchInsertAlertData(deviceData, alertRule, needNotifyIds);
        // 9. 使用web socket 向前端推送报警数据
        webSocketNotify(alertData, needNotifyIds , alertRule);
    }

    /**
     * 使用web socket 向前端推送报警数据
     *
     * @param alertData
     * @param needNotifyIds
     * @param alertRule
     */
    private void webSocketNotify(AlertData alertData, List<Long> needNotifyIds, AlertRule alertRule) {
        AlertNotifyVo alertNotifyVo = BeanUtil.toBean(alertData, AlertNotifyVo.class);
        alertNotifyVo.setFunctionName(alertRule.getFunctionName());
        alertNotifyVo.setAlertDataType(alertRule.getAlertDataType());
        alertNotifyVo.setNotifyType(1);
        webSocketServer.sendMessageToConsumer(alertNotifyVo, needNotifyIds);
    }

    /**
     * 批量插入报警数据
     *
     * @param deviceData
     * @param alertRule
     * @param needNotifyIds
     */
    private AlertData batchInsertAlertData(DeviceData deviceData, AlertRule alertRule, List<Long> needNotifyIds) {
        log.error("开始通知养老院工作人员：{}", needNotifyIds);
        // 1. 数据拷贝
        AlertData alertData = BeanUtil.toBean(deviceData, AlertData.class);
        // 2. 排除不需要的字段 并添加没有拷贝到的字段
        alertData.setId(null);
        alertData.setAlertRuleId(alertRule.getId());
            // 2.1 添加报警原因 格式：【功能名称】【运算符】【阈值】，持续 【持续周期】 个周期，沉默【沉默周期】分钟 ==> 温度 >= 40 , 持续5个周期，沉默5分钟
        StringBuilder sb = new StringBuilder();
        sb.append(alertRule.getFunctionName()).append(alertRule.getOperator()).append(alertRule.getValue())
                .append("持续").append(alertRule.getDuration()).append("个周期，沉默")
                .append(alertRule.getAlertSilentPeriod()).append("分钟");
        alertData.setAlertReason(sb.toString());
        alertData.setType(alertRule.getAlertDataType());
        alertData.setStatus(0);
            // 2.2 添加处理人id 所有数据都一样 只是处理人id不同
        List<AlertData> dbList = needNotifyIds.stream().map(id ->
                BeanUtil.toBean(alertData, AlertData.class).setUserId(id)
        ).collect(Collectors.toList());
        // 3. 批量插入
        alertDataService.saveBatch(dbList);
        // 4. 返回结果
        return alertData;
    }

}
